/*
Author Souvik - This driver is written purely from the learning perspective of
writing a USB client driver. 
I had another intention ie., to make the flashing of mini2440 fast because 
usbpush utility , which uses libusb , is found to be extremely slow.
This driver is the first version and is there are lots of scope for its 
improvement.The fusbpush ("Fast usbpush") application and this driver works 
hand in hand.
*/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/smp_lock.h> /* lock/unlock_kernel functions */
#include <linux/completion.h>
#include <asm/uaccess.h>
#include <linux/jiffies.h> /* HZ */
#include <linux/ioctl.h> /* IOCTL call added to handle abdrupt interrupt of 
                            application using Ctrl+C */
#include <fusbpush_ioctl.h>  /* Header file shared with user space */                             

//#undef FUSBPUSH_DBG 
#ifdef FUSBPUSH_DBG
    #define DRVDBG(fmt, args...) \
        printk( KERN_ALERT "fusbpush: " fmt, ## args)
#else
    #define DRVDBG(fmt, args...)
#endif

/* Obtained from lsusb -v */
#define MINI2440_VENDOR_ID	0x5345
#define MINI2440_PRODUCT_ID	0x1234

static int fusbpush_probe (struct usb_interface *intf, \
    const struct usb_device_id *id);
static void fusbpush_disconnect (struct usb_interface *intf);

ssize_t fusbpush_write(struct file *, const char __user *, size_t , loff_t *);
int fusbpush_open(struct inode *, struct file *);
int fusbpush_release(struct inode *, struct file *);
long fusbpush_ioctl(struct file *, unsigned int, unsigned long );

/* Ids for usbcore to pick up this driver when khubd detects the mini2440
being connected to a port of the root hub */
static struct usb_device_id  mini2440_id[] ={
    {USB_DEVICE(MINI2440_VENDOR_ID, MINI2440_PRODUCT_ID)},
    {}
};

/* mini2440 device specific structure */
typedef struct {
    struct usb_device *dev; /* Device representation */
    struct usb_interface *interface; /* Interface representation */  
    __u8 bulk_out_addr; 
    __u8 bulk_in_addr;
    size_t bulk_in_len;
    __u8 *bulk_in_buffer;
    struct completion sync;
    spinlock_t spinlock; 
    int urb_status;
    struct urb *purb;
}mini2440_device_t;

/* file operation structure exposed by mini2440 device driver */
static struct file_operations fusbpush_fops = 
{
    .write = fusbpush_write,
    .open = fusbpush_open,
    .unlocked_ioctl = fusbpush_ioctl,
    .release = fusbpush_release,
};

/* usb class driver structure object */
static struct usb_class_driver mini2440_class = 
{
    /* Yet to find out how we can create the file with 622 (4+2,2,2 i.e., 
    Read/Write, Write, Write) permissions */
    .name = "fusbpushDev", /* This becomes the name of the device node */
    .fops = &fusbpush_fops,
    .minor_base = 1, /* ref to page 353 Rubini 3rd edition for explanation */
};

/* MINI2440 usb driver structure */
static struct usb_driver fusbpush_driver = {
    .name = "fusbpushDrv",
    .id_table = mini2440_id,
    .probe = fusbpush_probe,
    .disconnect = fusbpush_disconnect,     
};

/* Called by usbcore when the VENDORID and PRODUCTID of mini2440 
matches with the mini2440_id */
static int fusbpush_probe (struct usb_interface *intf, \
    const struct usb_device_id *id)
{
    
    /* 
    First let us verify that that whether the end point criteria meets with 
    our mini2440 or not. See the dump of lsusb -v at the end of this file 
    */
    
    /* 
    Check whether the information obtained by khubd after device enumeration
    matches with the above device descriptor or not. Also populate the 
    mini2440_device object with the information obtained from interface 
    passed by usbcore in this function - ref to page 324 
    (Essential Device drivers)
    */
    
    struct usb_host_interface *iface_desc;
    struct usb_endpoint_descriptor *endpoint_desc;
    mini2440_device_t *pmini2440_device;
    
    /* retVal = 0, means if everything is OK, then we tell USB 
    core that this driver is claiming the interfaces of the enumerated device */
    int retVal = 0, i; 
    
    DRVDBG("Probe Called\n");
        
    /* Allocate a mini2440 device object */
    pmini2440_device = kmalloc(sizeof(mini2440_device_t), GFP_KERNEL);
    if(!pmini2440_device)
    {
        DRVDBG("Unable to allocate memory\n");
        retVal = -ENOMEM;
        goto err;   
    }

    /* Make the bulk out address = 0. This will get filled up while scanning 
    through the information obtained from the passed interface in this func
    */ 
    pmini2440_device->bulk_out_addr = 0;

    /* Also save the usb_interface and usb_device objects available in the passed 
    interface in this func inside mini2440 device desc */
    pmini2440_device->interface = intf; /* Save the interface */
    
    /* Set the purb to NULL */
    pmini2440_device->purb = NULL;
    
    /* Why can't I use interface_to_usbdev(interface) instead of usb_get_dev ??*/
    /* Save the usb_device information */
    pmini2440_device->dev = usb_get_dev(interface_to_usbdev(intf)); 

    
    /* Scan the device descriptor - Read the structure usb_interface - file usb.h */  
    iface_desc = intf->cur_altsetting;  /* We worry about current settings */
    
    
    /* From usb_host_interface -> usb_interface-descriptor -> to get all required
    information  - see usb.h and ch9.h */
    DRVDBG("Number of end points:%d", \
        iface_desc->desc.bNumEndpoints);
    
    /* Loop through the endpoints - Note : end point desc are found in the 
    structure usb_endpoint_descriptor, which is part of usb_host_interface which
    is again part of usb_interface */
    for(i = 0; i<iface_desc->desc.bNumEndpoints; i++)
    {
        endpoint_desc = &iface_desc->endpoint[i].desc;
        /* In this version. check the BULK_OUT only. Here it is checked whether 
         */
        if((!pmini2440_device->bulk_out_addr) && \
            (usb_endpoint_is_bulk_out(endpoint_desc)))
        {
            /* Save the BULK OUT end point address for future use */
            pmini2440_device->bulk_out_addr = endpoint_desc->bEndpointAddress;
            DRVDBG("BULK OUT endpoint is found to be :0x%x\n", \
                pmini2440_device->bulk_out_addr);            
            break; /* Break out as I got the required endpoint */
        }
    }
    
    /* Check whether BULK_OUT end point is found or not ? */
    if(!pmini2440_device->bulk_out_addr)    
    {
        DRVDBG("BULK OUT end point is not found !!\n");
        retVal = -EFAULT;
        goto err;
    }

    /* Initialize the spinlock */
    spin_lock_init(&pmini2440_device->spinlock);
    
    /* This is necessary because, if the user without doing any write 
    transaction sends a Ctrl+C (very unlikely), then in the IOCTL 
    the complete(&(pmini2440_device->sync)) call may crash the system*/
    init_completion(&pmini2440_device->sync);

    /* Save pmini2440_device inside usb_interface. This is required
    so that one can get pmini2440_device in the file ops entry points */
    usb_set_intfdata(intf, pmini2440_device);
   
    /* This driver is not going to use any other USB subsystem, so it
    needs to expose it's interface as character driver to the userland. 
    Now that we have got the endpoints , register the fops.
    */    
    retVal = usb_register_dev(intf, &mini2440_class);
    if(retVal)
    {
        DRVDBG("Unable to register usb device !!\n");
        usb_set_intfdata(intf, NULL);
        goto err;
    }
    else
    {
         DRVDBG("Registered usb device !!\n");   
    }     
    
     goto ok;   
     
err:     
   if(pmini2440_device)
        kfree(pmini2440_device); /* Free the allocated memory */
        
ok:   
    return retVal;   
}

static void fusbpush_disconnect (struct usb_interface *intf)
{
    mini2440_device_t    *pmini2440_device;
    
    DRVDBG("Disconnect Called\n");    
    
    /* To make sure that there is NO RACE condition between fusbpush_open
    and fusbpush_disconnect it is VERY IMPORTANT to lock this code piece */   
    lock_kernel();
    
    /* get the stored device object from intf - saved in the probe function */
    pmini2440_device = (mini2440_device_t *)usb_get_intfdata (intf);

    if(pmini2440_device)
    {
        /* Kill the URBs */
        if(pmini2440_device->purb)
            usb_kill_urb(pmini2440_device->purb);    
        
        /* Zero out the interface data */
        usb_set_intfdata(intf, NULL);
        
        /* Free mini2440_device_t object */
        pmini2440_device->interface = NULL;
        kfree(pmini2440_device);
    }    
    /* Release /dev/fusbpush */
    usb_deregister_dev(intf, &mini2440_class);    
    
    unlock_kernel();
}

/* Enry Points */

int fusbpush_open(struct inode *pinode, struct file *pfile)
{
    struct usb_interface *intf;
    struct mini2440_device_t *pmini2440_device;
    int retVal = 0;

    DRVDBG("Opened Called\n");    
    
    /* Get the interface from the minor number available in the inode */
    intf = usb_find_interface(&fusbpush_driver, iminor(pinode));
    if(intf)
    {
        /* Get "mini2440_device_t" object already stored in intf */   
        pmini2440_device = usb_get_intfdata(intf);
        if(pmini2440_device)
        {
            /* Save "pmini2440_device" in pfile->private_data so that we can use 
            the same easily without going through the above method */

            /* Save the data in private space so that it can be used from 
            other entry points*/            
            pfile->private_data = (mini2440_device_t *)pmini2440_device;
        }
        else
        {
            DRVDBG("Opened Failed because mini2440 deviece data is BAD  \n");    
            retVal = -ENODEV;          
        }
    }
    else
    {
        DRVDBG("Opened Failed because interface information is BAD \n");    
        retVal = -ENODEV;
    }

    return retVal;
}

/* BULK OUT write callback */
static void fusbpush_write_bulk_callback(struct urb *purb)
{
   
    mini2440_device_t *pmini2440_device;
    
    pmini2440_device = (mini2440_device_t *)purb->context;
    
    if(purb->status && (!(-ENOENT == purb->status) \
                                    || !(-ECONNRESET == purb->status) \
                                    || !(-ESHUTDOWN == purb->status)) )
    {
           DRVDBG("Non Zero write call back status received \n");    
           
           /*
            if(usb_submit_urb(purb, GFP_KERNEL))
            {
                DRVDBG("Failed to Re-submit URB\n");
                goto freeURB;
            }
            else
            {
                DRVDBG("Re-submitting URB\n");
                goto out;           
            }
            */
            spin_lock(&(pmini2440_device->spinlock));
            pmini2440_device->urb_status = purb->status;
            spin_unlock(&(pmini2440_device->spinlock));
    }

    
/* freeURB:   */

    /* Free the URB anyway */
    usb_free_coherent(purb->dev, 
                                    purb->transfer_buffer_length, 
                                    purb->transfer_buffer, 
                                    purb->transfer_dma);
                                                                 
     /* Submit completion */
     complete(&(pmini2440_device->sync));
     DRVDBG("URB completion signal\n");
/*     
out:
      return;                                 
*/
}


/* write */
ssize_t fusbpush_write(struct file *pfile, const char __user *buffer, \
    size_t size, loff_t *pOffset)
{
    /* We will pass the data passed by the user. This part may be optimized in the 
    future version od this driver. If the size passed is more than the size than 
    the BULK OUT endpoint can supports, then usb core does the fragmentation 
    of the data for us and sumits the URBs to the HCI. This is more efficient.
    */
    
    __u8 *driverDMAbuffer = NULL;
    
    mini2440_device_t *pmini2440_device = NULL;
    int retVal = 0;

    DRVDBG("Length of data passed from userland:%d\n", size);    
 
  
 
    /* Get the mini2440_device_st object which was stored in the private data 
    in driver open entry point */
    pmini2440_device = (mini2440_device_t *)pfile->private_data;
    if(!pmini2440_device)
    {
        DRVDBG("BAD  mini2440_device_st object\n");    
        retVal = -ENOMEM;
        goto err;
    }

    init_completion(&(pmini2440_device->sync));
    
    /* Allocate URB */    
    pmini2440_device->purb = usb_alloc_urb(0, GFP_KERNEL);
    if(!pmini2440_device->purb)
    {
        DRVDBG("Unable to allocate memory for URB\n");    
        retVal = -ENOMEM;
        goto err;
    }

    /* For efficient tranfer it is better to do DMA - Note "driverDMAbuffer" is the 
    CPU side buffer which can be used to perform the DMA operation*/
    driverDMAbuffer = usb_alloc_coherent(pmini2440_device->dev, \
        size, GFP_KERNEL, &pmini2440_device->purb->transfer_dma);
    if(!driverDMAbuffer)
    {
        DRVDBG("Unable to get CPU address to perform DMA for URB transfer\n");    
        retVal = -ENOMEM;
        goto err;
    }

    /* Copy data from user space */
    if(copy_from_user(driverDMAbuffer, buffer, size))
    {
        DRVDBG("Unable to copy data from user space\n");    
        retVal = -ENOMEM;
        goto err;
    }
    
    /* Now everything is Ready for Creating and sumitting the BULK OUT URB */
    
    /* Create BULK URB - Note the last parameter of usb_fill_bulk_urb. 
    This is the URB context which filled with  pmini2440_device so that in 
    fusbpush_write_bulk_callback one can use it if required.
    */
    
    usb_fill_bulk_urb(pmini2440_device->purb, pmini2440_device->dev,
                                usb_sndbulkpipe(pmini2440_device->dev, \
                                    pmini2440_device->bulk_out_addr), 
                                driverDMAbuffer, 
                                size, 
                                fusbpush_write_bulk_callback, 
                                pmini2440_device);

    pmini2440_device->purb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 

    /* Set the urb_status = 0 */
    pmini2440_device->urb_status = 0;
    /* Submit URB */
    retVal = usb_submit_urb(pmini2440_device->purb, GFP_KERNEL);
    if(retVal)
    {
        DRVDBG("Failed to submit URB\n");
        goto err;    
    }
    
    /* Wait for completion here */
    /* TODO - How do we find out whether we have received the signal due 
    to interrupt/timeout/actual signal from callback */
    /* Here waiting is for 5 secs */
    wait_for_completion_interruptible_timeout(&(pmini2440_device->sync), \
        (5*HZ));                               
    
    DRVDBG("Completion signal from write callback\n");

    usb_free_urb(pmini2440_device->purb);        
    pmini2440_device->purb = NULL;
    
    /* This is an updated urb_status variable sone in the write callback
    If urb_status = 0, the URB is OK as obtained in the write callback */
    
    if(!pmini2440_device->urb_status)
        retVal = size;
    else
        retVal = -1;    
    goto ok;
err:
    if(pmini2440_device->purb) 
    {
        usb_free_coherent(pmini2440_device->dev,
                                            size,
                                            driverDMAbuffer,
                                            pmini2440_device->purb->transfer_dma);
        usb_free_urb(pmini2440_device->purb);  
        pmini2440_device->purb = NULL;
    }   
    
ok:            
    
    return retVal; 
}

long fusbpush_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    mini2440_device_t *pmini2440_device = NULL;
    int retVal = 0;

    pmini2440_device = (mini2440_device_t *)pfile->private_data;
    
    switch(cmd)
    {
        case IOCTL_STOP_TRANSACTION:
        {
            /* Kill the URB - Should we kill the URB */            
            if(pmini2440_device->purb)
                usb_kill_urb(pmini2440_device->purb);
                
            /* Wake up the application which is blocked on the driver's write*/    
            complete(&(pmini2440_device->sync));    
            break;
        }
        default:
        {
            DRVDBG("BAD IOCTL called with number :%d\n", cmd);            
            retVal = -EINVAL;
            break; 
        }
    }

    return retVal;
    
}


int fusbpush_release(struct inode *pinode, struct file *pfile)
{
    DRVDBG("Release Called\n");    
    return 0;
}


/* In init routine, the usb_driver structure object needs to be registered. If this 
is done, then only usbcore will be able to call the probe function of this driver */
static int __init fusbpush_init(void)
{
    int result;
    
    result = usb_register(&fusbpush_driver);
    if(result)
        DRVDBG("Failed to register fusbpush: error no -:%d\n", result);        
    else
        DRVDBG("Successfully registered fusbpush driver\n");                
    return 0;
}

static void __exit fusbpush_exit(void)
{
    DRVDBG("Deregistering fusbpush driver\n");
    usb_deregister(&fusbpush_driver);                
}

module_init(fusbpush_init);
module_exit(fusbpush_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("fusbpush driver to push the binaries to MINI2440 \
    RAM for flashing");
MODULE_AUTHOR("Souvik Datta");


/*
DUMP from lsusb -v
--------------------------
Bus 002 Device 004: ID 5345:1234 Owon PDS6062T Oscilloscope
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass          255 Vendor Specific Class
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x5345 Owon
  idProduct          0x1234 PDS6062T Oscilloscope
  bcdDevice            1.00
  iManufacturer           1 System MCU
  iProduct                2 SEC S3C2410X Test B/D
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           32
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xc0
      Self Powered
    MaxPower               50mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0020  1x 32 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0020  1x 32 bytes
        bInterval               0
Device Status:     0x0001
  Self Powered
*/

